#ifndef _simodo_lsp_client_h
#define _simodo_lsp_client_h

#include "simodo/variable/json/Rpc.h"
#include "simodo/variable/json/Serialization.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/log/Logger_interface.h"
#include "simodo/lsp-client/ServerCapabilities.h"
#include "simodo/lsp-client/SimodoCapabilities.h"
#include "simodo/tp/ThreadsafeQueue.h"

#include <boost/process.hpp>

#include <iostream>
#include <functional>
#include <memory>
#include <thread>
#include <mutex>
#include <map>
#include <condition_variable>

namespace simodo::lsp
{
namespace bp = boost::process;

typedef std::function<void(const variable::JsonRpc &, const void *)> RpcHandle;

struct ServerInfo
{
    std::u16string  name;
    std::u16string  version;
};

struct InitializeResult
{
    ServerCapabilities  capabilities;
    SimodoCapabilities  simodo;
    ServerInfo          serverInfo;
};

class LanguageClient;

struct ServerRestartSupport_interface
{
    virtual ~ServerRestartSupport_interface() = default;

    virtual void LanguageServerRestarted(LanguageClient * client) = 0; 
};

class LanguageClient
{
    std::string                         _process_path;
    std::vector<std::string>            _process_args;
    variable::Object                    _initialize_params;
    inout::Logger_interface &           _log;
    /// @todo Доделать рестарт сервера! Для этого нужно:
    ///
    /// - в RpcHandle добавить ошибку?
    /// - реализовать восстановление сервера по открытым документам (интерфейс ServerRestartSupport_interface)
    // ServerRestartSupport_interface *    _server_restart_support = nullptr;

    bp::opstream                        _os;
    bp::ipstream                        _is;
    std::unique_ptr<bp::child>          _language_server;
    InitializeResult                    _initialize_result;
    mutable std::mutex                  _initialize_result_mutex;

    std::map<int64_t, std::pair<RpcHandle,const void *>>        
                                        _commands_waiting_to_be_executed;
    std::mutex                          _commands_waiting_to_be_executed_mutex;
    std::map<std::string, RpcHandle>    _commands_listeners;
    std::mutex                          _commands_listeners_mutex;
    std::condition_variable             _command_complete_condition;
    std::mutex                          _command_complete_condition_mutex;

    tp::ThreadsafeQueue<std::string>    _queue_for_sending;
    std::unique_ptr<std::thread>        _sending_thread;

    int64_t                             _last_id = 0;
    std::unique_ptr<std::thread>        _response_thread;
    bool                                _ok = true;
    bool                                _stopping = false;

public:
    LanguageClient(std::string process_path, std::vector<std::string> process_args, 
                   variable::Object initialize_params, inout::Logger_interface & logger,
                   ServerRestartSupport_interface * server_restart_support=nullptr);
    ~LanguageClient();

    InitializeResult    initialize_result() const;
    ServerCapabilities  server_capabilities() const;
    SimodoCapabilities  simodo_capabilities() const;

    int64_t exec(const std::string method, variable::Value params, RpcHandle handle=nullptr, const void * sender=nullptr);

    bool done(uint64_t timeout_mills=500);
    void close();

    bool ok() { return _ok; }
    bool running() { return _ok && !_stopping && _language_server && _language_server->running(); }
    bool registerListener(std::string method, RpcHandle listener);

private:
    bool start();
    int64_t exec(const variable::JsonRpc & rpc, RpcHandle handle, const void * sender);
    void sender();
    void response_listener();
    bool waitResponse(int64_t id, int timeout_mills = 5000);
    bool verifyJson(const std::string & str);

    InitializeResult parseInitializeResult(const variable::Value & result_value);
};

}

#endif //_simodo_lsp_client_h
